
/* GCSx
** SPAWNEDIT.CPP
**
** Spawn-point editor-only functionality
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

SpawnEdit::SpawnEdit(WorldEdit* myWorld, LayerEdit* myLayer, int newId) : Spawn(newId) { start_func
    assert(myWorld);

    world = myWorld;
    layer = myLayer;
    lockCount = 0;
    undoReady = 0;
}

SpawnEdit::~SpawnEdit() { start_func
    if (lockCount) {
        if (animgroup) animgroup->markUnlock();
        if (tileset) tileset->markUnlock();
    }
}

SpawnEdit::SpawnEdit(const SpawnEdit* copy, LayerEdit* myLayer, int newId) : Spawn(newId) { start_func
    world = copy->world;
    layer = myLayer;
    lockCount = 0;
    undoReady = 0;
    
    name = copy->name;
    script = copy->script;
    animgroup = copy->animgroup;
    tileset = copy->tileset;
    subid = copy->subid;
    x = copy->x;
    y = copy->y;
}

void SpawnEdit::setLayer(LayerEdit* newLayer) { start_func
    layer = newLayer;
}

void SpawnEdit::setUndoReady() { start_func
    undoReady = 1;
}
    
void SpawnEdit::setModified() { start_func
    // @TODO: if no layer..?
    if (layer) layer->setContentModified();
}

void SpawnEdit::calculateDisplayCoords() { start_func
    displayX = x;
    displayY = y;
    if (tileset) {
        displayW = tileset->getWidth();
        displayH = tileset->getHeight();
    }
    else if ((animgroup) && (lockCount)) {
        // (if not locked, animgroup isn't locked and we can't get info)
        // @TODO: anim group sizing
        displayW = 40;
        displayH = 40;
    }
    else {
        displayW = 40;
        displayH = 40;
    }
}

void SpawnEdit::setName(const std::string& newName, Window* srcWin, Window* exWin) throw_Undo { start_func
    if (name != newName) {
        setModified();
        if ((world) && (undoReady)) world->undo.storeUndoName(UndoBuffer::UNDO_SPAWNNAME, id, name, newName, srcWin);
        name = newName;
    
        desktop->broadcastObjChange(OBJ_SPAWN | OBJMOD_NAME, this, 0, 0, exWin);
    }
}

void SpawnEdit::setPos(int nX, int nY, int skipUndo, Window* srcWin, Window* exWin) throw_Undo { start_func
    if ((x != nX) || (y != nY)) {
        setModified();
        if ((world) && (undoReady) && (!skipUndo)) world->undo.storeUndoSpawnMove(id, layer->getId(), x, y, srcWin);
        x = nX;
        y = nY;
        calculateDisplayCoords();
    
        desktop->broadcastObjChange(OBJ_SPAWN | OBJMOD_POS, this, 0, 0, exWin);
    }
}

void SpawnEdit::setSprite(AnimGroup* newAnimgroup, TileSet* newTileset, int newId, Window* srcWin, Window* exWin) throw_File throw_Undo { start_func
    if ((newAnimgroup != animgroup) || (newTileset != tileset) || (newId != subid)) {
        assert(newId >= 0);
        if ((newAnimgroup) || (newTileset)) {
            assert(newId);
            if (newAnimgroup) assert(!newTileset);
            else assert(!newAnimgroup);
        }
        else assert(!newId);

        // Lock first (file exception point)
        if (lockCount) {
            if (newAnimgroup) newAnimgroup->markLock();
            if (newTileset) {
                try {
                    newTileset->markLock();
                }
                catch (...) {
                    if (newAnimgroup) newAnimgroup->markUnlock();
                    throw;
                }
            }
        }
        
        setModified();
        int aId = 0;
        int tId = 0;
        if (animgroup) aId = animgroup->getId();
        if (tileset) tId = tileset->getId();
        
        // Undo (if fail, undo locking from earlier)
        try {
            if ((world) && (undoReady)) world->undo.storeUndoSpawnSprite(id, layer->getId(), aId, tId, subid, srcWin);
        }
        catch (...) {
            if (lockCount) {
                if (newTileset) newTileset->markUnlock();
                if (newAnimgroup) newAnimgroup->markUnlock();
            }
            throw;
        }

        // Now that all exceptions are passed, unlock prior
        if (lockCount) {
            if (animgroup) animgroup->markUnlock();
            if (tileset) tileset->markUnlock();
        }
        
        animgroup = newAnimgroup;
        tileset = newTileset;
        subid = newId;
        calculateDisplayCoords();
    
        desktop->broadcastObjChange(OBJ_SPAWN | OBJMOD_IMAGE, this, 0, 0, exWin);
    }
}

void SpawnEdit::setScript(Script* newScript, Window* srcWin, Window* exWin) throw_Undo { start_func
    if (script != newScript) {
        setModified();
        int sId = 0;
        if (script) sId = script->getId();
        if ((world) && (undoReady)) world->undo.storeUndoSpawnScript(id, layer->getId(), sId, srcWin);
        script = newScript;
    
        desktop->broadcastObjChange(OBJ_SPAWN | OBJMOD_SCRIPT, this, 0, 0, exWin);
    }
}

int SpawnEdit::propertiesDialog(Window* srcWin, Window* exWin) { start_func
    string newName = name;
    AnimGroup* newAnimgroup = animgroup;
    TileSet* newTileset = tileset;
    Script* newScript = script;
    int newId = subid;
    
    int result;
    if (layer) {
        result = SpawnPropertiesDialog::createWithSprite()->run(&newName, &newScript, &newAnimgroup, &newTileset, &newId, world);
    }
    else {
        result = SpawnPropertiesDialog::createNoSprite()->run(&newName, &newScript, NULL, NULL, NULL, world);
    }

    if (result) {
        // (no locking of layer needed- if we're here, our owner is obviously in memory)
        try {
            if ((world) && (undoReady)) world->undo.preUndoBlock();
            setName(newName, srcWin, exWin);
            setScript(newScript, srcWin, exWin);
            if (layer) {
                // @TODO: throw_File
                setSprite(newAnimgroup, newTileset, newId, srcWin, exWin);
            }
            if ((world) && (undoReady)) world->undo.postUndoBlock();
            // (at least one of script or sprite must still be present now)
            assert((script) || (newTileset) || (newAnimgroup));
        }
        catch (UndoException& e) {
            return 0;
        }
        return 1;
    }
    
    return 0;
}

void SpawnEdit::save(class FileWrite* file) throw_File { start_func
    file->writeInt(id);
    file->writeStr(name);
    if (script) file->writeInt(script->getId());
    else file->writeInt(0);
    if (animgroup) file->writeInt(animgroup->getId());
    else file->writeInt(0);
    if (tileset) file->writeInt(tileset->getId());
    else file->writeInt(0);
    file->writeInt(subid);
    file->writeInt(x);
    file->writeInt(y);
}

void SpawnEdit::blit(int offsetX, int offsetY, SDL_Surface* dest, int clipX, int clipY, int clipW, int clipH, int dim, int selection) { start_func
    assert(lockCount);
    
    // Outside of range?
    if ((displayX >= clipX + clipW) ||
        (displayY >= clipY + clipH) ||
        (clipX >= displayX + displayW) ||
        (clipY >= displayY + displayH)) return;

    if (dim == 1) dim = 0x80;
    else if (dim) dim = 0xC0;
    else dim = 0xFF;
        
    // Display
    if (tileset) {
        dynamic_cast<TileSetEdit*>(tileset)->blitTileFx(subid, dest, offsetX + displayX,
                                                        offsetY + displayY,
                                                        dim * Image::ALPHA_DEFAULT / 0xFF,
                                                        0, 0, 0, Image::COLOR_DEFAULT);
    }
    //else if (animgroup) {
        // @TODO: animgroup display (base on x/y, as displayx/y would be offset already)
    //}
    else {
        SDL_Surface* src = getIconSurface();
        alphaBlitFx(60, 160, src, offsetX + displayX, offsetY + displayY, dest, displayW,
                    displayH, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, dim, 0xFF);
    }
    
    if (selection) {
        drawSelectRect(offsetX + displayX, offsetY + displayY, displayW, displayH,
                       guiPacked[COLOR_SELECTION1], dest);
        drawAntsBox(offsetX + displayX, offsetY + displayY, displayW, displayH,
                    desktop->currentSelectionPhase(), dest);
    }
}

int SpawnEdit::markLock() throw_File { start_func
    if (animgroup) animgroup->markLock();
    if (tileset) {
        try {
            tileset->markLock();
        }
        catch (...) {
            animgroup->markUnlock();
            throw;
        }
    }
    // (animgroup might not have been locked before)
    calculateDisplayCoords();
    return ++lockCount;
}

int SpawnEdit::markUnlock() { start_func
    --lockCount;
    assert(lockCount >= 0);
    if (lockCount == 0) {
        if (animgroup) animgroup->markUnlock();
        if (tileset) tileset->markUnlock();
    }
    return lockCount;
}

int SpawnEdit::isLocked() const { start_func
    return lockCount;
}

void SpawnEdit::load(FileRead* file, const World* world) throw_File { start_func
    AnimGroup* prevA = NULL;
    TileSet* prevT = NULL;
    if (lockCount) {
        // Unlock later, after all exception points
        prevA = animgroup;
        prevT = tileset;
        animgroup = NULL;
        tileset = NULL;
    }

    Spawn::load(file, world);

    if (lockCount) {
        try {
            if (animgroup) animgroup->markLock();
            try {
                if (tileset) tileset->markLock();
            }
            catch (...) {
                tileset->markUnlock();
                throw;
            }
        }
        catch (...) {
            // Revert to previous, still-locked items
            animgroup = prevA;
            tileset = prevT;
            throw;
        }
    }
    
    if (prevA) prevA->markUnlock();
    if (prevT) prevT->markUnlock();
    
    calculateDisplayCoords();
    undoReady = 1;
}
